Skip to content

fix: require complete, exact-length hex for Bytes/BytesN args - #2684

Open
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad
Open

fix: require complete, exact-length hex for Bytes/BytesN args#2684
Galmanus wants to merge 2 commits into
stellar:mainfrom
Galmanus:fix/2244-bytesn-no-zero-pad

Conversation

@Galmanus

Copy link
Copy Markdown

What

Removes the right-align zero-padding applied to hex input for Bytes and BytesN contract-invoke parameters. Now:

  • both require an even number of hex digits;
  • BytesN<N> requires exactly N bytes and errors (stating expected vs got) instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved.

Why

Closes #2244. Today typing e.g. ABCD for a BytesN<32> is silently accepted and zero-padded, which is surprising and dangerous — a mistyped fixed-length byte value is accepted rather than rejected. The arg_parsing help text already promises "bytesN (exactly N bytes)"; this makes that true.

Scope / safety

The shared helper padded_hex_from_str (utils.rs) is left untouched — it still backs contract-id and 32-byte salt parsing (5 other callers) that legitimately rely on padding. The change is confined to the BytesN/Bytes value-parse arms in soroban-spec-tools::from_string.

Breaking change

Yes — input previously accepted (short/odd hex for BytesN) now errors. Flagged here for release notes.

Tests

Added: under-length, over-length, and odd-hex rejection for BytesN; odd-hex for Bytes; exact-length still parses. All pre-existing bytes tests use exact-length input, so none change behavior. cargo test -p soroban-spec-tools and cargo clippy -p soroban-spec-tools --all-targets are green.

Removes the right-align zero-padding applied to hex input for Bytes and
BytesN contract-invoke parameters. Both now require an even number of hex
digits, and BytesN<N> requires exactly N bytes, erroring (expected vs got)
instead of silently zero-padding a short value.

The BytesN<32> strkey backwards-compat path is preserved, and the shared
padded_hex_from_str helper (still used by contract-id and salt parsing) is
left untouched, so only the invoke value-parse path changes.

Closes stellar#2244
Copilot AI balanced review requested due to automatic review settings August 15, 2026 00:51
@github-project-automation github-project-automation Bot moved this to Backlog (Not Ready) in DevX Aug 15, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR tightens hex-string parsing for Soroban Bytes/BytesN primitives to avoid silent zero-padding and to provide more specific error reporting for incomplete/invalid inputs.

Changes:

  • Enforce even-length hex input for Bytes and BytesN.
  • Require BytesN hex to decode to exactly N bytes (no padding / no truncation).
  • Add targeted tests covering odd-hex and BytesN under/over-length cases.
Suppressed comments (1)

cmd/crates/soroban-spec-tools/src/lib.rs:1

  • Adding new variants to a public enum is a SemVer-breaking change for downstream crates that exhaustively match on Error (unless the enum is already #[non_exhaustive]). Consider marking Error as #[non_exhaustive] (and updating any guidance/docs), or otherwise treat this as a major-version bump / avoid introducing new public variants by mapping into an existing variant.
#![allow(clippy::missing_errors_doc, clippy::must_use_candidate)]

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

#[error("invalid hex: expected an even number of hex digits, got {0}")]
OddHexLength(usize),
#[error(
"invalid length for BytesN<{expected}>: expected {expected} bytes but got {got} bytes; the input is incomplete"

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — the variant is returned for over-length too, so the input is incomplete was misleading. Dropped that clause in c98824f; the message is now "invalid length for BytesN: expected N bytes but got M bytes".

Comment on lines +932 to +942
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
let decoded = hex::decode(s).map_err(|_| Error::InvalidValue(Some(t.clone())))?;
if decoded.len() != bytes.n as usize {
return Err(Error::BytesNLengthMismatch {
expected: bytes.n as usize,
got: decoded.len(),
});
}
decoded

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

Comment on lines +951 to +959
if s.len() % 2 != 0 {
return Err(Error::OddHexLength(s.len()));
}
ScVal::Bytes(
hex::decode(s)
.map_err(|_| Error::InvalidValue(Some(t.clone())))?
.try_into()
.map_err(|_| Error::InvalidValue(Some(t.clone())))?,
)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Extracted the shared odd-length check + hex decode + error mapping into a decode_complete_hex helper in c98824f; both the BytesN and Bytes branches now call it.

…rror

Copilot review: (1) BytesNLengthMismatch is also returned for over-length
input, so drop 'the input is incomplete' from the message; (2) the odd-length
check + hex decode + error mapping were duplicated across the BytesN and Bytes
branches — extract decode_complete_hex().
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog (Not Ready)

Development

Successfully merging this pull request may close these issues.

when accepting bytes for a BytesN do not zero pad

2 participants